// Copyright (C) Calum Grant 2008

#include <cppscript>
#include <tls.hpp>

#include "unit_tests.hpp"


namespace
{
	void throws() 
	{ 
		throw var(); 
	}
}


void test_files()
{
	// Files
	var f1 = write_file("test.txt");
	f1["writeln"]("hello");
	f1["close"]();

	try
	{
		f1["writeln"]("this will fail");
		assert(0);
	}
	catch(var e)
	{
		assert( e.class_name() == "io_error" );
	}

	var f2 = read_file("test.txt");
	assert( f2["readln"]() == "hello" );
	f2["close"]();

	f2["readln"]();
	assert_throws( var, f2["read"]() );
	
	// fstat
	var stat = fstat("test.txt");
	assert( stat );
	assert( stat["size"] >= 6 );

	var stat2 = fstat("file_does_not_exist");
	assert( !stat2 );

	gc_force_collect();

	// Pickle
	{
	var object1 = object();
		object1["name"] = "Calum";
		object1["age"] = 34;

	pickle_file("test.pickle", object1);
	var object2 = unpickle_file("test.pickle");
	assert( lex_equal(object2, object1) );
	}

#if 0
	write("Enter your name: ");
	writeln("Your name is '" + readln()+"'");

	// Cout
	var stream = out();
	stream["writeln"]("Hello world!");
	stream["write"]("abc");
	stream["writech"]('X');

	// Cerr
	stream = err();
	stream["writeln"]("Hello error!");
	stream["write"]("def");
	stream["writech"]('Y');

	// Cin
	writeln( "Input name: ");
	writeln( "Your name is: " + in()["readln"]() );
#endif

	// The open_file functions throw if they fail to open the file
	assert_throws( var, read_file("no_such_file") );
	assert_throws( var, open_file("no_such_file", std::ios_base::in|std::ios_base::out) );
	assert_throws( var, write_file("") );


	// mem_file
	var m1 = mem_file();
	m1["writeln"]("Hello foo");		// memfile.writeln
	m1["rewind"]();							// memfile.rewind
	assertx( m1["readln"]() == "Hello foo" );	// memfile.readln
	assertx( m1["readln"]() == null );
	m1["rewind"]();

	// characters
	m1 = mem_file();
	m1["write"]("abc");
	m1["rewind"]();
	var s = "";
	foreach( ch, characters(m1) ) s += ch;
	assert( s=="abc" );

	// memfile.read
	m1["rewind"]();
	assert( m1["read"]() == "abc" );	

	assert_throws( var, m1["read"]() );

	// lines
	m1 = mem_file();
	m1["writeln"](123);
	m1["writeln"](456);
	var c=0;

	foreach( l, lines(m1) )
	{
		c++;
		if(c==1) assert( l=="123" );
		if(c==2) assert( l=="456" );
	}

	// Throwing a constructor must not screw up the garbage collector (regression test)

	try
	{
		foreach( l, lines( object().extend("readln",throws) ) )
			;
		assert(0 && "Exception not thrown");
	}
	catch(var x)
	{
		assert( x == null );
	}
}


void test_foreach()
{
	var sum=0;
	foreach(i, range(4, 5))
		sum += i;

	assert( sum == 9 );

	sum=0;

	foreach(i, range(4, 4) ) 
		sum += i;

	assert( sum == 4 );

	// Reversed range
	sum=0;
	foreach(i, range(3,2) ) sum += i;
	assert( sum == 5 );
	foreach(i, reverse(range(3,2)) ) sum -= i;
	assert( sum == 0 );

	foreach(i, array()) assert(0);
}


static void set_exit(var obj) 
{ 
	obj["exit"]=true; 
}


void test_scopeguard()
{
	var o = object();

	{
		finally( bind(set_exit, o) );
	}
	assert( o["exit"] );

	o=object();
	{
		scopeguard g1( bind(set_exit, o) );
		g1.dismiss();
	}
	assert( !o.contains("exit") );
}


void e1()
{
	throw exception();
}


void test_exception()
{
	try
	{
		(var(e1))();
	}
	catch(var v)
	{
		assert(v["text"] == "exception");
		assert(v["stack"].size() == 1);
		assert(v["stack"][0] == "script_main");

		try
		{
			throw;
		}
		catch(std::exception & ex)
		{
			assert(ex.what() == std::string("exception"));
		}
	}

	try
	{
		throw exception("my_class");
	}
	catch(var v)
	{
		assert(v.class_name() == "my_class");
		assert(v["text"] == "my_class");
		assert(v["data"] == var() );
		assert( exception_description(v) == "my_class (my_class)\n    called from script_main" );
	}

	try
	{
		throw exception("my_class2", "It happened");
	}
	catch(var v)
	{
		assert( v.class_name() == "my_class2" );
		assert( v["text"] == "It happened" );
		assert( v["data"] == var() );
		assert( exception_description(v) == "It happened (my_class2)\n    called from script_main" );
	}

	try
	{
		throw exception("my_class2", "abc", "def");
	}
	catch(var v)
	{
		assert( v.class_name() == "my_class2" );
		assert( v["text"] == "abc" );
		assert( v["data"] = "def" );
		assert( exception_description(v) == "abc (my_class2)\n    called from script_main" );
	}
}


void tf1(var obj)
{
	var s = stack_trace();
	assert( s.size() == 2 );
	assert( s[0].as_string() == "My A.f1" );

	obj["f2"]();

	s = stack_trace();
	assert( s.size() == 2 );
	assert( s[0].as_string() == "My A.f1" );
}


void tf2(var obj)
{
	var s = stack_trace();
	assert( s.size() == 3 );
	assert( s[0].as_string() == "My A.f2" );
	assert( s[1].as_string() == "My A.f1" );
}


static void thread_fn()
{
	var st = stack_trace();
	assert( st.size() == 1 );
	assert( st[0] == "thread" );
}


void test_stack_trace()
{
	assert( stack_trace().size() == 1 );
	var o = object("My A").extend("f1", tf1)("f2", tf2 );
	o["f1"]();
	assert( stack_trace().size() == 1 );

	thread(thread_fn)["join"]();
}


void test_tls()
{
	int i1=1, i2=2;
	assert( (cg::tls<int*,int>::get() == 0 ));
	cg::tls<int*,int>::set(&i1);
	assert( (cg::tls<int*,int>::get() == &i1 ));

	assert(( cg::tls<int*,char>::get() == 0 ));
	cg::tls<int*,char>::set(&i2);
	assert( (cg::tls<int*,char>::get() == &i2 ));
}


void test_framework2(var self)
{
	++self["count"];
}


void test_test_framework()
{
	// Can't test it failing since that would confuse the output

	var self = object().extend("test", test_framework2)("count",0);
	assertx( run_tests( map() ) == 0 );
	assertx( run_tests( map("test_tf1", self["test"])("test_tf2", self["test"]) ) == 0 );
	assertx( self["count"]==2 );
}


var always_true(var ch) 
{ 
	return true; 
}


void test_string_functions()
{
	// substring(string, range)
	assert( substring("hello", range(1,3)) == "ell" );
	assert( substring("", range(0,1)) == "" );
	assert( substring("abc", range(-1,-2) ) == "cb" );
	assert( substring("abc", range(-2,-1) ) == "bc" );
	assert( substring("abc", range(1,10) ) == "bc" );

	// string_find(string, pattern)
	assert( !string_find("hello", "xyz") );
	assert( string_find("hello", "ll") == range(2,3) );
	assert( string_find("", "") == null );
	assert( string_find("a", "") == range_ex(0,0) );

	// string_find_last(string, pattern)
	assert( !string_find_last("hello", "xyz") );
	assert( string_find_last("hellohello", "ll") == range(7,8) );
	assert( string_find_last("", "") == null );
	assert( string_find_last("ab", "") == range_ex(1,1) );

	// string_find_all(string, substring)
	assert( string_find_all("hello", "xyz").size() == 0 );
	var r1 = string_find_all("abcabcabab", "ab");
	assert( r1.size() == 4 );
	assert( r1[0] == range(0,1) );
	assert( r1[1] == range(3,4) );
	assert( r1[2] == range(6,7) );
	assert( r1[3] == range(8,9) );

	// split_chars(string, characters)
	r1 = split_chars(always_true, "abc");
	assert( r1.size() == 1 );
	assert( r1[0] == "abc" );

	r1 = split_chars( bind(is_one_of,"abc"), "abcdaddab" );
	assert( r1.size() == 3 );
	assert( r1[0] == "abc" );
	assert( r1[1] == "a" );
	assert( r1[2] == "ab" );

	// functors
	assert( is_space(' ') );
	assert( !is_space('x') );
	assert( is_one_of("abc", 'b') );
	assert( is_not_one_of("abc", 'x') );
}


void test_libs()
{
	assert( dynamic::min(1,2)==1 );
	assert( dynamic::max(1,2)==2 );
}
